using System;
using System.IO;
using System.Text;

namespace NAudio.Midi
{
    /// <summary>
    /// Represents a note MIDI event
    /// </summary>
    public class NoteEvent : MidiEvent
    {
        private int noteNumber;
        private int velocity;

        /// <summary>
        /// Reads a NoteEvent from a stream of MIDI data
        /// </summary>
        /// <param name="br">Binary Reader for the stream</param>
        public NoteEvent(BinaryReader br)
        {
            NoteNumber = br.ReadByte();
            velocity = br.ReadByte();
            // it seems it is possible for cubase
            // to output some notes with a NoteOff velocity > 127
            if (velocity > 127)
            {
                velocity = 127;
            }
        }

        /// <summary>
        /// Creates a MIDI Note Event with specified parameters
        /// </summary>
        /// <param name="absoluteTime">Absolute time of this event</param>
        /// <param name="channel">MIDI channel number</param>
        /// <param name="commandCode">MIDI command code</param>
        /// <param name="noteNumber">MIDI Note Number</param>
        /// <param name="velocity">MIDI Note Velocity</param>
        public NoteEvent(long absoluteTime, int channel, MidiCommandCode commandCode, int noteNumber, int velocity)
            : base(absoluteTime, channel, commandCode)
        {
            this.NoteNumber = noteNumber;
            this.Velocity = velocity;
        }

        private static readonly string[] NoteNames = new string[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };

        /// <summary>
        /// <see cref="MidiEvent.GetAsShortMessage" />
        /// </summary>
        public override int GetAsShortMessage()
        {
            return base.GetAsShortMessage() + (noteNumber << 8) + (velocity << 16);
        }

        /// <summary>
        /// The MIDI note number
        /// </summary>
        public virtual int NoteNumber
        {
            get
            {
                return noteNumber;
            }
            set
            {
                if (value < 0 || value > 127)
                {
                    throw new ArgumentOutOfRangeException("value", "Note number must be in the range 0-127");
                }
                noteNumber = value;
            }
        }

        /// <summary>
        /// The note velocity
        /// </summary>
        public int Velocity
        {
            get
            {
                return velocity;
            }
            set
            {
                if (value < 0 || value > 127)
                {
                    throw new ArgumentOutOfRangeException("value", "Velocity must be in the range 0-127");
                }
                velocity = value;
            }
        }

        /// <summary>
        /// The note name
        /// </summary>
        public string NoteName
        {
            get
            {
                if ((Channel == 16) || (Channel == 10))
                {
                    switch (noteNumber)
                    {
                        case 35: return "Acoustic Bass Drum";
                        case 36: return "Bass Drum 1";
                        case 37: return "Side Stick";
                        case 38: return "Acoustic Snare";
                        case 39: return "Hand Clap";
                        case 40: return "Electric Snare";
                        case 41: return "Low Floor Tom";
                        case 42: return "Closed Hi-Hat";
                        case 43: return "High Floor Tom";
                        case 44: return "Pedal Hi-Hat";
                        case 45: return "Low Tom";
                        case 46: return "Open Hi-Hat";
                        case 47: return "Low-Mid Tom";
                        case 48: return "Hi-Mid Tom";
                        case 49: return "Crash Cymbal 1";
                        case 50: return "High Tom";
                        case 51: return "Ride Cymbal 1";
                        case 52: return "Chinese Cymbal";
                        case 53: return "Ride Bell";
                        case 54: return "Tambourine";
                        case 55: return "Splash Cymbal";
                        case 56: return "Cowbell";
                        case 57: return "Crash Cymbal 2";
                        case 58: return "Vibraslap";
                        case 59: return "Ride Cymbal 2";
                        case 60: return "Hi Bongo";
                        case 61: return "Low Bongo";
                        case 62: return "Mute Hi Conga";
                        case 63: return "Open Hi Conga";
                        case 64: return "Low Conga";
                        case 65: return "High Timbale";
                        case 66: return "Low Timbale";
                        case 67: return "High Agogo";
                        case 68: return "Low Agogo";
                        case 69: return "Cabasa";
                        case 70: return "Maracas";
                        case 71: return "Short Whistle";
                        case 72: return "Long Whistle";
                        case 73: return "Short Guiro";
                        case 74: return "Long Guiro";
                        case 75: return "Claves";
                        case 76: return "Hi Wood Block";
                        case 77: return "Low Wood Block";
                        case 78: return "Mute Cuica";
                        case 79: return "Open Cuica";
                        case 80: return "Mute Triangle";
                        case 81: return "Open Triangle";
                        default: return String.Format("Drum {0}", noteNumber);
                    }
                }
                else
                {
                    int octave = noteNumber / 12;
                    return String.Format("{0}{1}", NoteNames[noteNumber % 12], octave);
                }
            }
        }

        /// <summary>
        /// Describes the Note Event
        /// </summary>
        /// <returns>Note event as a string</returns>
        public override string ToString()
        {
            return String.Format("{0} {1} Vel:{2}",
                base.ToString(),
                this.NoteName,
                this.Velocity);
        }

        /// <summary>
        /// <see cref="MidiEvent.Export"/>
        /// </summary>
        public override void Export(ref long absoluteTime, BinaryWriter writer)
        {
            base.Export(ref absoluteTime, writer);
            writer.Write((byte)noteNumber);
            writer.Write((byte)velocity);
        }
    }
}